package com.yix.admin.modules.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.yix.admin.modules.system.entity.SysMenu;
import com.yix.admin.modules.system.entity.SysRole;
import com.yix.admin.modules.system.entity.SysUser;
import com.yix.admin.modules.system.entity.dto.SysMenuDto;
import com.yix.admin.modules.system.entity.dto.SysRoleSmallDto;
import com.yix.admin.modules.system.entity.mapper.SysMenuMapper;
import com.yix.admin.modules.system.entity.query.SysMenuQuery;
import com.yix.admin.modules.system.entity.vo.SysMenuMetaVo;
import com.yix.admin.modules.system.entity.vo.SysMenuVo;
import com.yix.admin.modules.system.repository.SysMenuRepository;
import com.yix.admin.modules.system.repository.SysUserRepository;
import com.yix.admin.modules.system.service.SysMenuService;
import com.yix.admin.modules.system.service.SysRoleService;
import com.yix.common.exception.BadRequestException;
import com.yix.common.exception.EntityExistException;
import com.yix.common.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wdx
 * @version 1.0
 * @Description TODO
 * @date 2021/1/27 16:44
 */
@Service
@CacheConfig(cacheNames = "menu")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysMenuServiceImpl implements SysMenuService {
    @Autowired
    private SysMenuRepository sysMenuRepository;
    @Autowired
    private SysUserRepository sysUserRepository;
    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private RedisUtils redisUtils;

    @Override
    public List<SysMenuDto> buildTree(List<SysMenuDto> menuDtos) {
        List<SysMenuDto> trees = new ArrayList<>();
        Set<String> ids = new HashSet<>();
        for (SysMenuDto menuDTO : menuDtos) {
            if (menuDTO.getPid() == null) {
                trees.add(menuDTO);
            }
            for (SysMenuDto it : menuDtos) {
                if (menuDTO.getId().equals(it.getPid())) {
                    if (menuDTO.getChildren() == null) {
                        menuDTO.setChildren(new ArrayList<>());
                    }
                    menuDTO.getChildren().add(it);
                    ids.add(it.getId());
                }
            }
        }
        if(trees.size() == 0){
            trees = menuDtos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList());
        }
        return trees;
    }

    @Override
    public List<SysMenuVo> buildMenus(List<SysMenuDto> menuDtos) {
        List<SysMenuVo> list = new LinkedList<>();
        menuDtos.forEach(menuDTO -> {
                    if (menuDTO!=null){
                        SysMenuVo menuVo = new SysMenuVo();
                        menuVo.setName(ObjectUtil.isNotEmpty(menuDTO.getComponentName())  ? menuDTO.getComponentName() : menuDTO.getName());
                        // 一级目录需要加斜杠，不然会报警告
                        menuVo.setPath(menuDTO.getPid() == null ? "/" + menuDTO.getPath() :menuDTO.getPath());
                        menuVo.setHidden(menuDTO.getHidden());
                        // 如果不是外链
                        if(!menuDTO.getIFrame()){
                            if(menuDTO.getPid() == null){
                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"Layout":menuDTO.getComponent());
                                // 如果不是一级菜单，并且菜单类型为目录，则代表是多级菜单
                            }else if(menuDTO.getType() == 0){
                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"ParentView":menuDTO.getComponent());
                            }else if(StringUtils.isNoneBlank(menuDTO.getComponent())){
                                menuVo.setComponent(menuDTO.getComponent());
                            }
                        }
                        menuVo.setMeta(new SysMenuMetaVo(menuDTO.getName(),menuDTO.getIcon(),menuDTO.getCache()==null?false:menuDTO.getCache()));
                        List<SysMenuDto> menuDtoList = menuDTO.getChildren();
                        if(CollectionUtil.isNotEmpty(menuDtoList)){
                            menuVo.setAlwaysShow(true);
                            menuVo.setRedirect("noredirect");
                            menuVo.setChildren(buildMenus(menuDtoList));
                            // 处理是一级菜单并且没有子菜单的情况
                        } else if(menuDTO.getPid() == null){
                            SysMenuVo menuVo1 = new SysMenuVo();
                            menuVo1.setMeta(menuVo.getMeta());
                            // 非外链
                            if(!menuDTO.getIFrame()){
                                menuVo1.setPath("index");
                                menuVo1.setName(menuVo.getName());
                                menuVo1.setComponent(menuVo.getComponent());
                            } else {
                                menuVo1.setPath(menuDTO.getPath());
                            }
                            menuVo.setName(null);
                            menuVo.setMeta(null);
                            menuVo.setComponent("Layout");
                            List<SysMenuVo> list1 = new ArrayList<>();
                            list1.add(menuVo1);
                            menuVo.setChildren(list1);
                        }
                        list.add(menuVo);
                    }
                }
        );
        return list;
    }

    @Override
    @Cacheable
    public List<SysMenuDto> queryAll(SysMenuQuery criteria){
//        Sort sort = new Sort(Sort.Direction.DESC,"id");
        return sysMenuMapper.toDto(sysMenuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
    }

    @Override
    public List<SysMenuDto> queryAll(SysMenuQuery criteria, Boolean isQuery) throws Exception {
        Sort sort = new Sort(Sort.Direction.ASC, "menuSort");
        if(isQuery){
            criteria.setPidIsNull(true);
            List<Field> fields = QueryHelp.getAllFields(criteria.getClass(), new ArrayList<>());
            for (Field field : fields) {
                //设置对象的访问权限，保证对private的属性的访问
                field.setAccessible(true);
                Object val = field.get(criteria);
                if("pidIsNull".equals(field.getName())){
                    continue;
                }
                if (ObjectUtil.isNotNull(val)) {
                    criteria.setPidIsNull(null);
                    break;
                }
            }
        }
        return sysMenuMapper.toDto(sysMenuRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),sort));
    }

    /**
     * 用户角色改变时需清理缓存
     * @param currentUserId /
     * @return /
     */
    @Override
    @Cacheable(key = "'user:' + #p0")
    public List<SysMenuDto> findByUser(String currentUserId) {
        List<SysRoleSmallDto> roles = sysRoleService.findByUsersId(currentUserId);
        if(roles.size()>0){
            Set<String> roleIds = roles.stream().map(SysRoleSmallDto::getId).collect(Collectors.toSet());
            LinkedHashSet<SysMenu> menus = sysMenuRepository.findByRoleIdsAndTypeNot(roleIds, 2);
            return menus.stream().map(sysMenuMapper::toDto).collect(Collectors.toList());
        }else {
            return new ArrayList<>();
        }

    }

    @Override
    @Cacheable(key = "'id:' + #p0")
    public SysMenuDto findById(String id) {
        SysMenu menu = sysMenuRepository.findById(id).orElseGet(SysMenu::new);
        ValidationUtil.isNull(menu.getId(),"SysMenu","id",id);
        return sysMenuMapper.toDto(menu);
    }

    @Override
    public List<SysMenuDto> getSuperior(SysMenuDto menuDto, List<SysMenu> menus) {
        if(menuDto.getPid() == null){
            menus.addAll(sysMenuRepository.findByPidIsNull());
            return sysMenuMapper.toDto(menus);
        }
        menus.addAll(sysMenuRepository.findByPid(menuDto.getPid()));
        return getSuperior(findById(menuDto.getPid()), menus);
    }

    @Override
    public List<SysMenuDto> getMenus(String pid) {
        List<SysMenu> menus;
        if(pid != null && !pid.equals("0")){
            menus = sysMenuRepository.findByPid(pid);
        } else {
            menus = sysMenuRepository.findByPidIsNull();
        }
        return sysMenuMapper.toDto(menus);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void create(SysMenu resources) {
        if(sysMenuRepository.findByName(resources.getName()) != null){
            throw new EntityExistException(SysMenu.class,"title",resources.getName());
        }
        if(StringUtils.isNotBlank(resources.getComponentName())){
            if(sysMenuRepository.findByComponentName(resources.getComponentName()) != null){
                throw new EntityExistException(SysMenu.class,"componentName",resources.getComponentName());
            }
        }
        if(resources.getPid().equals("0")){
            resources.setPid(null);
        }
        if(resources.getIFrame()){
            String http = "http://", https = "https://";
            if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) {
                throw new BadRequestException("外链必须以http://或者https://开头");
            }
        }
        resources.setCreaterId(SecurityUtils.getCurrentUserId());
        sysMenuRepository.save(resources);
        // 计算子节点数目
        resources.setSubCount(0);
        // 更新父节点菜单数目
        updateSubCnt(resources.getPid());
    }

    private void updateSubCnt(String menuId){
        if(menuId != null){
            int count = sysMenuRepository.countByPid(menuId);
            sysMenuRepository.updateSubCntById(count, menuId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(SysMenu resources) {
        if(resources.getId().equals(resources.getPid())) {
            throw new BadRequestException("上级不能为自己");
        }
        SysMenu menu = sysMenuRepository.findById(resources.getId()).orElseGet(SysMenu::new);
        ValidationUtil.isNull(menu.getId(),"Permission","id",resources.getId());

        if(resources.getIFrame()){
            String http = "http://", https = "https://";
            if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) {
                throw new BadRequestException("外链必须以http://或者https://开头");
            }
        }
        SysMenu menu1 = sysMenuRepository.findByName(resources.getName());

        if(menu1 != null && !menu1.getId().equals(menu.getId())){
            throw new EntityExistException(SysMenu.class,"title",resources.getName());
        }

        if(resources.getPid().equals("0")){
            resources.setPid(null);
        }

        // 记录的父节点ID
        String oldPid = menu.getPid();
        String newPid = resources.getPid();

        if(StringUtils.isNotBlank(resources.getComponentName())){
            menu1 = sysMenuRepository.findByComponentName(resources.getComponentName());
            if(menu1 != null && !menu1.getId().equals(menu.getId())){
                throw new EntityExistException(SysMenu.class,"componentName",resources.getComponentName());
            }
        }
        menu.setName(resources.getName());
        menu.setComponent(resources.getComponent());
        menu.setPath(resources.getPath());
        menu.setIcon(resources.getIcon());
        menu.setIFrame(resources.getIFrame());
        menu.setPid(resources.getPid());
        menu.setMenuSort(resources.getMenuSort());
        menu.setCache(resources.getCache());
        menu.setHidden(resources.getHidden());
        menu.setComponentName(resources.getComponentName());
        menu.setPermission(resources.getPermission());
        menu.setType(resources.getType());
        menu.setUpdaterId(SecurityUtils.getCurrentUserId());
        sysMenuRepository.save(menu);
        // 计算父级菜单节点数目
        updateSubCnt(oldPid);
        updateSubCnt(newPid);
        // 清理缓存
        delCaches(resources.getId());
    }

    /**
     * 清理缓存
     * @param id 菜单ID
     */
    public void delCaches(String id){
        List<SysUser> users = sysUserRepository.findByMenuId(id);
        redisUtils.del(CacheKey.MENU_ID + id);
        redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(SysUser::getId).collect(Collectors.toSet()));
        // 清除 Role 缓存
        List<SysRole> roles = sysRoleService.findInMenuId(new ArrayList<String>(){{
            add(id);
        }});
        redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(SysRole::getId).collect(Collectors.toSet()));
    }

    @Override
    public SysMenu findOne(String id) {
        SysMenu menu = sysMenuRepository.findById(id).orElseGet(SysMenu::new);
        ValidationUtil.isNull(menu.getId(),"Menu","id",id);
        return menu;
    }

    @Override
    public Set<SysMenu> getChildMenus(List<SysMenu> menuList, Set<SysMenu> menuSet) {
        for (SysMenu menu : menuList) {
            menuSet.add(menu);
            List<SysMenu> menus = sysMenuRepository.findByPid(menu.getId());
            if(menus!=null && menus.size()!=0){
                getChildMenus(menus, menuSet);
            }
        }
        return menuSet;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<SysMenu> menuSet) {
        for (SysMenu menu : menuSet) {
            // 清理缓存
            delCaches(menu.getId());
            sysRoleService.untiedMenu(menu.getId());
            sysMenuRepository.deleteById(menu.getId());
            updateSubCnt(menu.getPid());
        }
    }
}
